//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using EffiProz.Core.Lib;
using EffiProz.Core.Rights;


namespace EffiProz.Core
{

    // fredt@users 20020130 - patch 497872 by Nitin Chauhan - reordering for speed
    // fredt@users 20020215 - patch 1.7.0 by fredt - support GROUP BY with more than one column
    // fredt@users 20020215 - patch 1.7.0 by fredt - SQL standard quoted identifiers
    // fredt@users 20020218 - patch 1.7.0 by fredt - DEFAULT keyword
    // fredt@users 20020221 - patch 513005 by sqlbob@users - SELECT INTO types
    // fredt@users 20020425 - patch 548182 by skitt@users - DEFAULT enhancement
    // thertz@users 20020320 - patch 473613 by thertz - outer join condition bug
    // fredt@users 20021229 - patch 1.7.2 by fredt - new solution for above
    // fredt@users 20020420 - patch 523880 by leptipre@users - VIEW support
    // fredt@users 20020525 - patch 559914 by fredt@users - SELECT INTO logging
    // tony_lai@users 20021020 - patch 1.7.2 - improved aggregates and HAVING
    // aggregate functions can now be used _in expressions - HAVING supported
    // kloska@users 20021030 - patch 1.7.2 - ON UPDATE CASCADE
    // fredt@users 20021112 - patch 1.7.2 by Nitin Chauhan - use of switch
    // rewrite of the majority of multiple if(){}else{} chains with switch(){}
    // boucherb@users 20030705 - patch 1.7.2 - prepared statement support
    // fredt@users 20030819 - patch 1.7.2 - EXTRACT({YEAR | MONTH | DAY | HOUR | MINUTE | SECOND } FROM datetime)
    // fredt@users 20030820 - patch 1.7.2 - CHAR_LENGTH | CHARACTER_LENGTH | OCTET_LENGTH(string)
    // fredt@users 20030820 - patch 1.7.2 - POSITION(string IN string)
    // fredt@users 20030820 - patch 1.7.2 - SUBSTRING(string FROM pos [FOR length])
    // fredt@users 20030820 - patch 1.7.2 - TRIM({LEADING | TRAILING | BOTH} [<character>] FROM <string expression>)
    // fredt@users 20030820 - patch 1.7.2 - CASE [expr] WHEN ... THEN ... [ELSE ...] END and its variants
    // fredt@users 20030820 - patch 1.7.2 - NULLIF(expr,expr)
    // fredt@users 20030820 - patch 1.7.2 - COALESCE(expr,expr,...)
    // fredt@users 20031012 - patch 1.7.2 - improved scoping for column names _in all areas
    // boucherb@users 200403xx - patch 1.7.2 - added support for prepared SELECT INTO
    // boucherb@users 200403xx - doc 1.7.2 - some
    // thomasm@users 20041001 - patch 1.7.3 - bool undefined handling
    // fredt@users 20050220 - patch 1.8.0 - CAST with precision / scale
    /* todo: fredt - implement remaining numeric value functions (SQL92 6.6)
     *
     * EXTRACT({TIMEZONE_HOUR | TIMEZONE_MINUTE} FROM {datetime | interval})
     */

    /**
     * Responsible for parsing non-DDL statements.
     *
     * Extensively rewritten and extended _in successive versions of HSQLDB.
     *
     * @author Thomas Mueller (Hypersonic SQL Group)
     * @version 1.8.0
     * @since Hypersonic SQL
     */
    class Parser
    {

        private Database database;
        private Tokenizer tokenizer;
        private Session session;
        private string sSchema;
        private string sTable;
        private string sToken;
        private bool wasQuoted;
        private Object oData;
        private int iType;
        private int iToken;
        private bool compilingView;

        //
        private int subQueryLevel;
        private List<SubQuery> subQueryList = new List<SubQuery>();

        /**
         *  Constructs a new Parser object with the given context.
         *
         * @param  db the Database instance against which to resolve named
         *      database object references
         * @param  t the token source from which to parse commands
         * @param  session the connected context
         */
        public Parser(Session session, Database db, Tokenizer t)
        {

            database = db;
            tokenizer = t;
            this.session = session;
        }

        /**
         *  sets a flag indicating the parser is used for compiling a view
         */
        public void setCompilingView()
        {
            compilingView = true;
        }

        /**
         *  determines whether the parser is used for compiling a view
         */
        public bool isCompilingView()
        {
            return compilingView;
        }

        /**
         *  Resets this parse context with the given SQL character sequence.
         *
         * Internal structures are reset as though a new parser were created
         * with the given sql and the originally specified database and session
         *
         * @param a new SQL character sequence to replace the current one
         */
        public void reset(string sql)
        {

            sTable = null;
            sToken = null;
            oData = null;

            tokenizer.reset(sql);
            subQueryList.Clear();

            subQueryLevel = 0;

            parameters.Clear();
        }

        /**
         * Tests whether the parsing session has the given write access on the
         * given Table object. <p>
         *
         * @param table the Table object to check
         * @param userRight the numeric code of the right to check
         * @  if the session user does not have the right
         *      or the given Table object is simply not writable (e.g. is a
         *      non-updateable View)
         */
        public void checkTableWriteAccess(Table table,
                                   int userRight)
        {

            // session level user rights
            session.checkReadWrite();

            // object level user rights
            session.check(table.getName(), userRight);

            // object type
            if (table.isView())
            {
                throw Trace.error(Trace.NOT_A_TABLE, table.getName().name);
            }

            // object _readonly
            table.checkDataReadOnly();
        }

        /**
         * Parses a comma-separated, right-bracket terminated list of column
         * names. <p>
         *
         * @param db the Database instance whose name manager is to provide the
         *      resulting HsqlNameManager.QName objects, when the full argument is true
         * @param t the tokenizer representing the character sequence to be parsed
         * @param full if true, generate a list of HsqlNames, else a list of
         *  string objects
         */
        public static List<object> getColumnNames(Database db, Table table, Tokenizer t,
                                            bool full)
        {

            List<object> columns = new List<object>();

            while (true)
            {
                if (full)
                {
                    string tokenx = t.getSimpleName();
                    bool quoted = t.wasQuotedIdentifier();
                    QNameManager.QName name = db.nameManager.newHsqlName(tokenx, quoted);

                    columns.Add(name);
                }
                else
                {
                    columns.Add(t.getName());

                    if (t.wasLongName()
                            && !t.getLongNameFirst().Equals(
                                table.getName().name))
                    {
                        throw (Trace.error(Trace.TABLE_NOT_FOUND,
                                           t.getLongNameFirst()));
                    }
                }

                string token = t.getSimpleToken();

                if (token.Equals(Token.T_COMMA))
                {
                    continue;
                }

                if (token.Equals(Token.T_CLOSEBRACKET))
                {
                    break;
                }

                t.throwUnexpected();
            }

            return columns;
        }

        /**
         * The SubQuery objects are added to the end of subquery list.
         *
         * When parsing the SELECT for a view, optional QName[] array is used
         * for view column aliases.
         *
         */
        public SubQuery parseSubquery(int brackets, QNameManager.QName[] colNames,
                               bool resolveAll,
                               int predicateType)
        {

            SubQuery sq;

            sq = new SubQuery();

            subQueryLevel++;

            bool canHaveOrder = predicateType == Expression.VIEW
                                   || predicateType == Expression.SELECT;
            bool canHaveLimit = predicateType == Expression.SELECT
                                   || predicateType == Expression.VIEW
                                   || predicateType == Expression.QUERY;
            bool limitWithOrder = predicateType == Expression.IN
                                     || predicateType == Expression.ALL
                                     || predicateType == Expression.ANY;
            Select s = parseSelect(brackets, canHaveOrder, canHaveLimit,
                                   limitWithOrder, true);

            sq.level = subQueryLevel;

            subQueryLevel--;

            bool isResolved = s.resolveAll(session, resolveAll);

            sq.select = s;
            sq.isResolved = isResolved;

            // it's not a problem that this table has not a unique name
            QNameManager.QName sqtablename =
                database.nameManager.newHsqlName("SYSTEM_SUBQUERY", false);

            sqtablename.schema = SchemaManager.SYSTEM_SCHEMA_HSQLNAME;

            Table table = new Table(database, sqtablename, Table.SYSTEM_SUBQUERY);

            if (colNames != null)
            {
                if (colNames.Length != s.iResultLen)
                {
                    throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
                }

                for (int i = 0; i < s.iResultLen; i++)
                {
                    QNameManager.QName name = colNames[i];

                    s.exprColumns[i].setAlias(name.name, name.isNameQuoted);
                }
            }
            else
            {
                for (int i = 0; i < s.iResultLen; i++)
                {
                    string colname = s.exprColumns[i].getAlias();

                    if (colname == null || colname.Length == 0)
                    {

                        // fredt - this does not guarantee the uniqueness of column
                        // names but addColumns() will throw if names are not unique.
                        colname = "COL_" + (i + 1).ToString();

                        s.exprColumns[i].setAlias(colname, false);
                    }
                }
            }

            table.addColumns(s);

            bool uniqueValues = predicateType == Expression.EXISTS
                                   || predicateType == Expression.IN
                                   || predicateType == Expression.ALL
                                   || predicateType == Expression.ANY;
            int[] pcol = null;

            if (uniqueValues)
            {
                pcol = new int[s.iResultLen];

                ArrayUtil.fillSequence(pcol);
            }

            table.createPrimaryKey(pcol);

            sq.table = table;
            sq.uniqueRows = uniqueValues;

            subQueryList.Add(sq);

            return sq;
        }

        public SubQuery getViewSubquery(View v)
        {

            SubQuery sq = v.viewSubQuery;

            for (int i = 0; i < v.viewSubqueries.Length; i++)
            {
                subQueryList.Add(v.viewSubqueries[i]);
            }

            return sq;
        }

        /**
         *  Constructs and returns a Select object.
         *
         * @param canHaveOrder whether the SELECT being parsed can have an ORDER BY
         * @param canHaveLimit whether LIMIT without ORDER BY is allowed
         * @param limitWithOrder whether LIMIT is allowed only with ORDER BY
         * @param isMain whether the SELECT being parsed is the first
         * select statement _in the set
         * @return a new Select object
         * @throws  HsqlException if a parsing error occurs
         */
        public Select parseSelect(int brackets, bool canHaveOrder,
                           bool canHaveLimit, bool limitWithOrder,
                           bool isMain)
        {

            Select select = new Select();
            string token = tokenizer.getString();

            if (canHaveLimit || limitWithOrder)
            {
                if (tokenizer.wasThis(Token.T_LIMIT)
                        || tokenizer.wasThis(Token.T_TOP))
                {
                    parseLimit(token, select, false);

                    token = tokenizer.getString();
                }
            }

            if (tokenizer.wasThis(Token.T_DISTINCT))
            {
                select.isDistinctSelect = true;
            }
            else if (tokenizer.wasThis(Token.T_ALL)) { }
            else
            {
                tokenizer.back();
            }

            // parse column list
            List<Expression> vcolumn = new List<Expression>();

            do
            {
                int expPos = tokenizer.getPosition();
                Expression e = parseExpression();

                if (isCompilingView())
                {
                    if (e.getType() == Expression.ASTERISK)
                    {
                        if (select.asteriskPositions == null)
                        {
                            select.asteriskPositions = new Dictionary<int, object>();
                        }

                        // remember the position of the asterisk. For the moment, just
                        // remember the expression, so it can later be found and replaced
                        // with the concrete column list
                        select.asteriskPositions.Add(expPos, e);
                    }
                }

                token = tokenizer.getString();

                if (tokenizer.wasThis(Token.T_AS))
                {
                    e.setAlias(tokenizer.getSimpleName(),
                               tokenizer.wasQuotedIdentifier());

                    token = tokenizer.getString();
                }
                else if (tokenizer.wasSimpleName())
                {
                    e.setAlias(token, tokenizer.wasQuotedIdentifier());

                    token = tokenizer.getString();
                }

                vcolumn.Add(e);
            } while (tokenizer.wasThis(Token.T_COMMA));

            if (token.Equals(Token.T_INTO))
            {
                bool getname = true;

                token = tokenizer.getString();
                select.intoType = database.getDefaultTableType();

                if (tokenizer.wasSimpleToken())
                {
                    switch (Token.get(token))
                    {

                        case Token.CACHED:
                            select.intoType = Table.CACHED_TABLE;
                            break;

                        case Token.TEMP:
                            select.intoType = Table.TEMP_TABLE;
                            break;

                        case Token.TEXT:
                            select.intoType = Table.TEXT_TABLE;
                            break;

                        case Token.MEMORY:
                            select.intoType = Table.MEMORY_TABLE;
                            break;

                        default:
                            getname = false;
                            break;
                    }

                    if (getname)
                    {
                        token = tokenizer.getName();
                    }
                }

                if (!tokenizer.wasName())
                {
                    tokenizer.throwUnexpected();
                }

                select.sIntoTable = database.nameManager.newHsqlName(token,
                        tokenizer.wasQuotedIdentifier());
                select.sIntoTable.schema =
                    session.getSchemaHsqlName(tokenizer.getLongNameFirst());
                token = tokenizer.getString();
            }

            tokenizer.matchThis(Token.T_FROM);

            Expression condition = null;

            // parse table list
            List<TableFilter> vfilter = new List<TableFilter>();

            vfilter.Add(parseTableFilter(false));

            while (true)
            {
                token = tokenizer.getString();

                bool cross = false;

                if (tokenizer.wasThis(Token.T_INNER))
                {
                    tokenizer.getThis(Token.T_JOIN);

                    token = Token.T_JOIN;
                }
                else if (tokenizer.wasThis(Token.T_CROSS))
                {
                    tokenizer.getThis(Token.T_JOIN);

                    token = Token.T_JOIN;
                    cross = true;
                }

                if (token.Equals(Token.T_LEFT)
                        && !tokenizer.wasQuotedIdentifier())
                {
                    tokenizer.isGetThis(Token.T_OUTER);
                    tokenizer.getThis(Token.T_JOIN);

                    TableFilter tf = parseTableFilter(true);

                    vfilter.Add(tf);
                    tokenizer.getThis(Token.T_ON);

                    Expression newcondition = parseExpression();

                    newcondition.checkTables(vfilter);

                    condition = addJoinCondition(condition, newcondition, tf,
                                                 true);

                    // MarcH HuugO RIGHT JOIN SUPPORT
                }
                else if (token.Equals(Token.T_RIGHT)
                         && !tokenizer.wasQuotedIdentifier())
                {
                    tokenizer.isGetThis(Token.T_OUTER);
                    tokenizer.getThis(Token.T_JOIN);

                    // this object is not an outerjoin, the next object is an outerjoin
                    TableFilter tf = parseTableFilter(false);

                    // insert new condition as first element _in a new vfilter (nvfilter), copy the content of vfilter and rename nvfilter back to vfilter.
                    List<TableFilter> nvfilter = new List<TableFilter>();

                    nvfilter.Add(tf);
                    nvfilter.InsertRange(nvfilter.Count, vfilter);

                    vfilter = nvfilter;

                    // set isOuterJoin correct
                    ((TableFilter)vfilter[(1)]).isOuterJoin = true;

                    tokenizer.getThis(Token.T_ON);

                    Expression newcondition = parseExpression();

                    newcondition.checkTables(vfilter);

                    condition = addJoinCondition(condition, newcondition,
                                                 ((TableFilter)vfilter[(1)]),
                                                 true);
                }
                else if (tokenizer.wasThis(Token.T_JOIN))
                {
                    vfilter.Add(parseTableFilter(false));

                    if (!cross)
                    {
                        tokenizer.getThis(Token.T_ON);

                        Expression newcondition = parseExpression();

                        newcondition.checkTables(vfilter);

                        condition = addJoinCondition(condition, newcondition,
                                                     null, false);
                    }
                }
                else if (tokenizer.wasThis(Token.T_COMMA))
                {
                    vfilter.Add(parseTableFilter(false));
                }
                else
                {
                    tokenizer.back();

                    break;
                }
            }

            resolveSelectTableFilter(select, vcolumn, vfilter);

            // where
            token = tokenizer.getString();

            if (tokenizer.wasThis(Token.T_WHERE))
            {
                Expression newcondition = parseExpression();

                condition = addCondition(condition, newcondition);
                token = tokenizer.getString();
            }

            select.queryCondition = condition;

            // group by
            if (tokenizer.wasThis(Token.T_GROUP))
            {
                tokenizer.getThis(Token.T_BY);

                int len = 0;

                do
                {
                    Expression e = parseExpression();

                    vcolumn.Add(e);

                    token = tokenizer.getString();

                    len++;
                } while (tokenizer.wasThis(Token.T_COMMA));

                select.iGroupLen = len;
            }

            // having
            if (tokenizer.wasThis(Token.T_HAVING))
            {
                select.iHavingLen = 1;
                select.havingCondition = parseExpression();
                token = tokenizer.getString();

                vcolumn.Add(select.havingCondition);
            }

            if (isMain || limitWithOrder)
            {
                if (tokenizer.wasThis(Token.T_ORDER))
                {
                    tokenizer.getThis(Token.T_BY);
                    parseOrderBy(select, vcolumn);

                    token = tokenizer.getString();
                }

                if (tokenizer.wasThis(Token.T_LIMIT))
                {
                    parseLimit(token, select, true);

                    token = tokenizer.getString();
                }
            }

            bool closebrackets = false;

            if (brackets > 0 && token.Equals(Token.T_CLOSEBRACKET))
            {
                closebrackets = true;
                brackets -= parseCloseBrackets(brackets - 1) + 1;
                token = tokenizer.getString();
            }

            select.unionDepth = brackets;

            // checks for ORDER and LIMIT
            if (!(isMain || closebrackets))
            {
                limitWithOrder = false;
            }

            bool hasOrder = select.iOrderLen != 0;
            bool hasLimit = select.limitCondition != null;

            if (limitWithOrder)
            {
                if (hasLimit && !hasOrder)
                {
                    throw Trace.error(Trace.ORDER_LIMIT_REQUIRED);
                }
            }
            else
            {
                if (hasOrder && !canHaveOrder)
                {
                    throw Trace.error(Trace.INVALID_ORDER_BY);
                }

                if (hasLimit && !canHaveLimit)
                {
                    throw Trace.error(Trace.INVALID_LIMIT);
                }
            }

            int unionType = parseUnion(token);

            if (unionType != Select.NOUNION)
            {
                bool openbracket = false;

                select.unionType = unionType;

                if (tokenizer.isGetThis(Token.T_OPENBRACKET))
                {
                    openbracket = true;
                    brackets += parseOpenBrackets() + 1;
                }

                tokenizer.getThis(Token.T_SELECT);

                // accept ORDRY BY with LIMIT when _in brackets
                select.unionSelect = parseSelect(brackets, false, false,
                                                 openbracket, false);
                token = tokenizer.getString();
            }

            if (isMain && (canHaveOrder || limitWithOrder)
                    && select.iOrderLen == 0)
            {
                if (tokenizer.wasThis(Token.T_ORDER))
                {
                    tokenizer.getThis(Token.T_BY);
                    parseOrderBy(select, vcolumn);

                    token = tokenizer.getString();
                    select.sortUnion = true;
                }

                if (tokenizer.wasThis(Token.T_LIMIT))
                {
                    parseLimit(token, select, true);

                    token = tokenizer.getString();
                }
            }

            tokenizer.back();

            if (isMain)
            {
                select.prepareUnions();
            }

            select.exprColumns = vcolumn.ToArray();

            return select;
        }

        /**
         * Parses the given token and any further tokens _in tokenizer to return
         * any UNION or other set operation ID.
         */
        public int parseUnion(string token)
        {

            int unionType = Select.NOUNION;

            if (tokenizer.wasSimpleToken())
            {
                switch (Token.get(token))
                {

                    case Token.UNION:
                        token = tokenizer.getSimpleToken();

                        if (token.Equals(Token.T_ALL))
                        {
                            unionType = Select.UNIONALL;
                        }
                        else if (token.Equals(Token.T_DISTINCT))
                        {
                            unionType = Select.UNION;
                        }
                        else
                        {
                            unionType = Select.UNION;

                            tokenizer.back();
                        }
                        break;

                    case Token.INTERSECT:
                        tokenizer.isGetThis(Token.T_DISTINCT);

                        unionType = Select.INTERSECT;
                        break;

                    case Token.EXCEPT:
                    case Token.MINUS:
                        tokenizer.isGetThis(Token.T_DISTINCT);

                        unionType = Select.EXCEPT;
                        break;

                    default:
                        break;
                }
            }

            return unionType;
        }

        // fredt@users 20011010 - patch 471710 by fredt - LIMIT rewritten
        // SELECT LIMIT n m DISTINCT ... queries and error message
        // "SELECT LIMIT n m ..." creates the result set for the SELECT statement then
        // discards the first n rows and returns m rows of the remaining result set
        // "SELECT LIMIT 0 m" is equivalent to "SELECT TOP m" or "SELECT FIRST m"
        // _in other RDBMS's
        // "SELECT LIMIT n 0" discards the first n rows and returns the remaining rows
        // fredt@users 20020225 - patch 456679 by hiep256 - TOP keyword
         private void parseLimit(string token, Select select,
                                bool isEnd)
        {

            if (select.limitCondition != null)
            {
                return;
            }

            Expression e1 = null;
            Expression e2;
            bool islimit = false;

            if (isEnd)
            {
                if (token.Equals(Token.T_LIMIT))
                {
                    islimit = true;

                    read();

                    e2 = readTerm();

                    if (sToken.Equals(Token.T_OFFSET))
                    {
                        read();

                        e1 = readTerm();
                    }

                    tokenizer.back();
                }
                else
                {
                    return;
                }
            }
            else if (token.Equals(Token.T_LIMIT))
            {
                read();

                e1 = readTerm();
                e2 = readTerm();
                islimit = true;

                tokenizer.back();
            }
            else if (token.Equals(Token.T_TOP))
            {
                read();

                e2 = readTerm();

                tokenizer.back();
            }
            else
            {
                return;
            }

            if (e1 == null)
            {
                e1 = new Expression(Types.INTEGER, (0));
            }

            if (e1._isParam()
                    || (e1.getType() == Expression.VALUE
                        && e1.getDataType() == Types.INTEGER
                        && e1.getValue(null) != null
                        && ((int)e1.getValue(null)) >= 0))
            {
                if (e2._isParam()
                        || (e2.getType() == Expression.VALUE
                            && e2.getDataType() == Types.INTEGER
                            && e2.getValue(null) != null
                            && ((int)e2.getValue(null)) >= 0))
                {

                    // necessary for params
                    e1.setDataType(Types.INTEGER);
                    e2.setDataType(Types.INTEGER);

                    select.limitCondition = new Expression(Expression.LIMIT, e1,
                                                           e2);

                    return;
                }
            }

            int messageid = islimit ? Trace.INVALID_LIMIT_EXPRESSION
                                    : Trace.INVALID_TOP_EXPRESSION;

            throw Trace.error(Trace.WRONG_DATA_TYPE, messageid);
        }

        private void parseOrderBy(Select select,
                                  List<Expression> vcolumn)
        {

            string token;
            int len = 0;

            do
            {
                Expression e = parseExpression();

                e = resolveOrderByExpression(e, select, vcolumn);
                token = tokenizer.getString();

                if (token.Equals(Token.T_DESC))
                {
                    e.setDescending();

                    token = tokenizer.getString();
                }
                else if (token.Equals(Token.T_ASC))
                {
                    token = tokenizer.getString();
                }

                vcolumn.Add(e);

                len++;
            } while (token.Equals(Token.T_COMMA));

            tokenizer.back();

            select.iOrderLen = len;
        }

        private void resolveSelectTableFilter(Select select,
                                              List<Expression> vcolumn,
                                              List<TableFilter> vfilter)
        {

            int colcount;
            TableFilter[] filters = vfilter.ToArray();



            select.tFilter = filters;

            // expand [table.]* columns
            colcount = vcolumn.Count;

            for (int pos = 0; pos < colcount; )
            {
                Expression e = (Expression)(vcolumn[pos]);

                if (e.getType() == Expression.ASTERISK)
                {
                    vcolumn.Remove(e);

                    colcount = vcolumn.Count;

                    string tablename = e.getTableName();
                    int oldPos = pos;

                    if (tablename == null)
                    {
                        for (int i = 0; i < filters.Length; i++)
                        {
                            pos = addFilterColumns(filters[i], vcolumn, pos);
                            colcount = vcolumn.Count;
                        }
                    }
                    else
                    {
                        TableFilter f = e.findTableFilter(filters);

                        if (f == null)
                        {
                            throw Trace.error(Trace.TABLE_NOT_FOUND, tablename);
                        }

                        pos = addFilterColumns(f, vcolumn, pos);
                        colcount = vcolumn.Count;
                    }

                    if (isCompilingView())
                    {

                        // find this expression's position _in the Select's asterisk list
                        bool foundAsteriskPos = false;


                        foreach (var expPos in select.asteriskPositions.Keys)
                        {


                            if (e == select.asteriskPositions[(expPos)])
                            {

                                // compile the complete column list which later is to replace the asterisk
                                StringBuilder completeColList = new StringBuilder();

                                for (int col = oldPos; col < pos; ++col)
                                {
                                    Expression resolvedColExpr =
                                        (Expression)(vcolumn[(col)]);

                                    completeColList.Append(
                                        resolvedColExpr.getColumnDDL());

                                    if (col < pos - 1)
                                    {
                                        completeColList.Append(",");
                                    }
                                }

                                select.asteriskPositions[expPos] = completeColList.ToString();
                            

                                foundAsteriskPos = true;

                                break;
                            }
                        }

                        Trace.doAssert(foundAsteriskPos);
                    }
                }
                else
                {
                    if (e.getFilter() == null)
                    {
                        for (int i = 0; i < filters.Length; i++)
                        {
                            e.resolveTables(filters[i]);
                        }
                    }

                    pos++;
                }
            }

            for (int i = 0; i < colcount; i++)
            {
                Expression e = (Expression)(vcolumn[(i)]);

                e.resolveTypes(session);
            }

            select.iResultLen = colcount;
        }

        /**
         * Add all columns of a table filter to list of columns
         */
        public int addFilterColumns(TableFilter filter, List<Expression> columnList,
                             int position)
        {

            Table table = filter.getTable();
            int count = table.getColumnCount();

            for (int i = 0; i < count; i++)
            {
                Expression e = new Expression(filter, table.getColumn(i));

                if (isCompilingView())
                {
                    e.resolveTables(filter);
                }

                columnList.Insert(position++, e);
            }

            return position;
        }

        /**
         * Resolves an ORDER BY Expression, returning the column Expression object
         * to which it refers if it is an alias or column index. <p>
         *
         * If select is a SET QUERY, then only column indexes or names _in the first
         * query are allowed.
         *
         * @param  e                          search column expression
         * @param  vcolumn                    list of columns
         * @param  union                      is select a union
         * @return                            new or the same expression
         * @  if an ambiguous reference to an alias or
         *      non-integer column index is encountered
         */
        private static Expression resolveOrderByExpression(Expression e,
                Select select, List<Expression> vcolumn)
        {

            int visiblecols = select.iResultLen;
            bool union = select.unionSelect != null;

            if (e.getType() == Expression.VALUE)
            {
                return resolveOrderByColumnIndex(e, vcolumn, visiblecols);
            }

            if (e.getType() != Expression.COLUMN)
            {
                if (union)
                {
                    throw Trace.error(Trace.INVALID_ORDER_BY);
                }

                return e;
            }

            string ecolname = e.getColumnName();
            string etablename = e.getTableName();

            for (int i = 0, size = visiblecols; i < size; i++)
            {
                Expression colexpr = (Expression)vcolumn[(i)];
                string colalias = colexpr.getDefinedAlias();
                string colname = colexpr.getColumnName();
                string tablename = colexpr.getTableName();
                string filtername = colexpr.getFilterTableName();

                if ((ecolname.Equals(colalias) || ecolname.Equals(colname))
                        && (etablename == null || etablename.Equals(tablename)
                            || etablename.Equals(filtername)))
                {
                    colexpr.joinedTableColumnIndex = i;

                    return colexpr;
                }
            }

            if (union)
            {
                throw Trace.error(Trace.INVALID_ORDER_BY, ecolname);
            }

            return e;
        }

        private static Expression resolveOrderByColumnIndex(Expression e,
                List<Expression> vcolumn, int visiblecols)
        {

            // order by 1,2,3
            if (e.getDataType() == Types.INTEGER)
            {
                int i = ((int)e.getValue(null));

                if (0 < i && i <= visiblecols)
                {
                    Expression colexpr = (Expression)vcolumn[(i - 1)];

                    colexpr.joinedTableColumnIndex = i - 1;

                    return colexpr;
                }
            }

            throw Trace.error(Trace.INVALID_ORDER_BY);
        }

        private TableFilter parseSimpleTableFilter(int type)
        {

            string alias = null;
            string token = tokenizer.getName();
            string schema = session.getSchemaName(tokenizer.getLongNameFirst());
            Table table = database.schemaManager.getTable(session, token, schema);

            checkTableWriteAccess(table, type);

            //
            token = tokenizer.getString();

            if (token.Equals(Token.T_AS))
            {
                alias = tokenizer.getSimpleName();
            }
            else if (tokenizer.wasSimpleName())
            {
                alias = token;
            }
            else
            {
                tokenizer.back();
            }

            return new TableFilter(table, alias, null, false);
        }

        /**
         * Retrieves a TableFilter object newly constructed from the current
         * parse context. <p>
         *
         * @param  outerjoin if the filter is to back an outer join
         * @return a newly constructed TableFilter object
         * @throws  HsqlException if a parsing error occurs
         */
        private TableFilter parseTableFilter(bool outerjoin)
        {

            Table t = null;
            SubQuery sq = null;
            string sAlias = null;
            HashMappedList<string,Expression> columnList = null;

            if (tokenizer.isGetThis(Token.T_OPENBRACKET))
            {
                int brackets = parseOpenBrackets();

                tokenizer.getThis(Token.T_SELECT);

                // fredt - not correlated - a joined subquery table must resolve fully
                sq = parseSubquery(brackets, null, true, Expression.QUERY);

                tokenizer.getThis(Token.T_CLOSEBRACKET);

                t = sq.table;
            }
            else
            {
                string tokenx = tokenizer.getName();
                string schema =
                    session.getSchemaName(tokenizer.getLongNameFirst());

                t = database.schemaManager.getTable(session, tokenx, schema);

                session.check(t.getName(), GrantConstants.SELECT);

                if (t.isView())
                {
                    sq = getViewSubquery((View)t);
                    sq.select = ((View)t).viewSelect;
                    t = sq.table;
                    sAlias = tokenx;
                }
            }

            // fredt - we removed LEFT from the list of reserved words _in Tokenizer
            // to allow LEFT() to work. Thus wasName() will return true for LEFT
            // and we check separately for this token
            string token = tokenizer.getString();

            if (tokenizer.wasLongName())
            {
                tokenizer.throwUnexpected();
            }

            if ((token.Equals(Token.T_LEFT) || token.Equals(Token.T_RIGHT))
                    && !tokenizer.wasQuotedIdentifier())
            {
                tokenizer.back();
            }
            else if (token.Equals(Token.T_AS)
                     && !tokenizer.wasQuotedIdentifier())
            {
                sAlias = tokenizer.getSimpleName();

                if (tokenizer.isGetThis(Token.T_OPENBRACKET))
                {
                    tokenizer.back();

                    columnList = parseColumnList();
                }
            }
            else if (tokenizer.wasSimpleName())
            {
                sAlias = token;

                if (tokenizer.isGetThis(Token.T_OPENBRACKET))
                {
                    tokenizer.back();

                    columnList = parseColumnList();
                }
            }
            else
            {
                tokenizer.back();
            }

            if (columnList != null && t.getColumnCount() != columnList.Count)
            {
                throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
            }

            return new TableFilter(t, sAlias, columnList, outerjoin);
        }

        /**
         *  Add a condition from the WHERE clause.
         *
         * @param  e1
         * @param  e2
         * @return
         */
        private static Expression addCondition(Expression e1, Expression e2)
        {

            if (e1 == null)
            {
                return e2;
            }
            else if (e2 == null)
            {
                return e1;
            }
            else
            {
                return new Expression(Expression.AND, e1, e2);
            }
        }

        /**
         *  Conjuntively adds a condition from the JOIN table ON clause.
         *
         * @param  e1 an existing condition with which e2 is to be combined
         *      _in order to form a new conjunction
         * @param  e2 the new condition
         * @param tf the table filter that should become e2's join
         *      table filter
         * @param outer true if join is outer
         * @  if e2 responds that it cannot participate
         *      _in the join
         * @return a new Expression object; the conjunction of e1 and e2
         */
        private static Expression addJoinCondition(Expression e1, Expression e2,
                TableFilter tf, bool outer)
        {

            if (!e2.setForJoin(tf, outer))
            {
                throw Trace.error(Trace.OUTER_JOIN_CONDITION);
            }

            return addCondition(e1, e2);
        }

        /**
         *  Method declaration
         *
         * @return the Expression resulting from the parse
         * @throws  HsqlException
         */
        public Expression parseExpression()
        {

            read();

            Expression r = readOr();

            tokenizer.back();

            return r;
        }

        private Expression readAggregate()
        {

            bool distinct = false;
            bool all = false;
            int type = iToken;

            read();

            string token = tokenizer.getString();

            if (token.Equals(Token.T_DISTINCT))
            {
                distinct = true;
            }
            else if (token.Equals(Token.T_ALL))
            {
                all = true;
            }
            else
            {
                tokenizer.back();
            }

            readThis(Expression.OPEN);

            Expression s = readOr();

            readThis(Expression.CLOSE);

            if ((all || distinct)
                    && (type == Expression.STDDEV_POP
                        || type == Expression.STDDEV_SAMP
                        || type == Expression.VAR_POP
                        || type == Expression.VAR_SAMP))
            {
                throw Trace.error(Trace.INVALID_FUNCTION_ARGUMENT);
            }

            Expression aggregateExp = new Expression(type, s, null);

            aggregateExp.setDistinctAggregate(distinct);

            return aggregateExp;
        }

        /**
         *  Method declaration
         *
         * @return a disjuntion, possibly degenerate
         * @throws  HsqlException
         */
        private Expression readOr()
        {

            Expression r = readAnd();

            while (iToken == Expression.OR)
            {
                int type = iToken;
                Expression a = r;

                read();

                r = new Expression(type, a, readAnd());
            }

            return r;
        }

        /**
         *  Method declaration
         *
         * @return a conjunction, possibly degenerate
         * @throws  HsqlException
         */
        private Expression readAnd()
        {

            Expression r = readCondition();

            while (iToken == Expression.AND)
            {
                int type = iToken;
                Expression a = r;

                read();

                r = new Expression(type, a, readCondition());
            }

            return r;
        }

        /**
         *  Method declaration
         *
         * @return a predicate, possibly composite
         * @throws  HsqlException
         */
        private Expression readCondition()
        {

            switch (iToken)
            {

                case Expression.NOT:
                    {
                        int type = iToken;

                        read();

                        return new Expression(type, readCondition(), null);
                    }
                case Expression.EXISTS:
                    {
                        int type = iToken;

                        read();
                        readThis(Expression.OPEN);

                        int brackets = 0;

                        if (iToken == Expression.OPEN)
                        {
                            brackets += parseOpenBrackets() + 1;

                            read();
                        }

                        Trace.check(iToken == Expression.SELECT,
                                    Trace.UNEXPECTED_TOKEN);

                        SubQuery sq = parseSubquery(brackets, null, false,
                                                    Expression.EXISTS);
                        Expression s = new Expression(sq);

                        read();
                        readThis(Expression.CLOSE);

                        return new Expression(type, s, null);
                    }
                default:
                    {
                        Expression a = readConcat();

                        if (iToken == Expression.IS)
                        {
                            read();

                            bool notx;

                            if (iToken == Expression.NOT)
                            {
                                notx = true;

                                read();
                            }
                            else
                            {
                                notx = false;
                            }

                            Trace.check(iToken == Expression.VALUE && oData == null,
                                        Trace.UNEXPECTED_TOKEN);
                            read();

                            // TODO: the TableFilter needs a right hand side to avoid null pointer exceptions...
                            a = new Expression(Expression.IS_NULL, a,
                                               new Expression(Types.NULL, null));

                            if (notx)
                            {
                                a = new Expression(Expression.NOT, a, null);
                            }

                            return a;
                        }

                        bool not = false;

                        if (iToken == Expression.NOT)
                        {
                            not = true;

                            read();
                        }

                        switch (iToken)
                        {

                            case Expression.LIKE:
                                {
                                    a = parseLikePredicate(a);

                                    break;
                                }
                            case Expression.BETWEEN:
                                {
                                    a = parseBetweenPredicate(a);

                                    break;
                                }
                            case Expression.IN:
                                {
                                    a = this.parseInPredicate(a);

                                    break;
                                }
                            default:
                                {
                                    Trace.check(!not, Trace.UNEXPECTED_TOKEN);

                                    if (Expression.isCompare(iToken))
                                    {
                                        int type = iToken;

                                        read();

                                        return new Expression(type, a, readConcat());
                                    }

                                    return a;
                                }
                        }

                        if (not)
                        {
                            a = new Expression(Expression.NOT, a, null);
                        }

                        return a;
                    }
            }
        }

        private Expression parseLikePredicate(Expression a)
        {

            read();

            Expression b = readConcat();
            char escape = '\0';

            if (sToken.Equals(Token.T_ESCAPE))
            {
                read();

                Expression c = readTerm();

                Trace.check(c.getType() == Expression.VALUE, Trace.INVALID_ESCAPE);

                string s = (String)c.getValue(session, Types.VARCHAR);

                // boucherb@users 2003-09-25
                // CHECKME:
                // Assert s.Length  == 1 for xxxchar comparisons?
                // TODO:
                // SQL200n says binary escape can be 1 or more octets.
                // Maybe we need to retain s and check this in
                // Expression.resolve()?
                if (s == null || s.Length < 1)
                {
                    throw Trace.error(Trace.INVALID_ESCAPE, s);
                }

                escape = s[(0)];
            }

            bool hasCollation = database.collation.name != null;

            a = new Expression(a, b, escape, hasCollation);

            return a;
        }

        private Expression parseBetweenPredicate(Expression a)
        {

            read();

            Expression l = new Expression(Expression.BIGGER_EQUAL, a,
                                          readConcat());

            readThis(Expression.AND);

            Expression h = new Expression(Expression.SMALLER_EQUAL, a,
                                          readConcat());

            if (l.getArg().isParam() && l.getArg2().isParam())
            {
                throw Trace.error(Trace.UNRESOLVED_PARAMETER_TYPE,
                                  Trace.Parser_ambiguous_between1);
            }

            if (h.getArg().isParam() && h.getArg2().isParam())
            {
                throw Trace.error(Trace.UNRESOLVED_PARAMETER_TYPE,
                                  Trace.Parser_ambiguous_between1);
            }

            return new Expression(Expression.AND, l, h);
        }

        private Expression parseInPredicate(Expression a)
        {

            int type = iToken;

            read();
            readThis(Expression.OPEN);

            Expression b = null;
            int brackets = 0;

            if (iToken == Expression.OPEN)
            {
                brackets += parseOpenBrackets() + 1;

                read();
            }

            if (iToken == Expression.SELECT)
            {
                SubQuery sq = parseSubquery(brackets, null, false, Expression.IN);

                // until we support rows _in IN predicates
                Trace.check(sq.select.iResultLen == 1,
                            Trace.SINGLE_COLUMN_EXPECTED);

                b = new Expression(sq);

                read();
            }
            else
            {
                tokenizer.back();

                List<Expression> v = new List<Expression>();

                while (true)
                {
                    Expression value = parseExpression();

                    if (value.exprType == Expression.VALUE
                            && value.valueData == null && !value._isParam())
                    {
                        throw Trace.error(Trace.NULL_IN_VALUE_LIST);
                    }

                    v.Add(value);
                    read();

                    if (iToken != Expression.COMMA)
                    {
                        break;
                    }
                }

                Expression[] valueList;

                valueList = (Expression[])v.ToArray();
                b = new Expression(valueList);
            }

            readThis(Expression.CLOSE);

            return new Expression(type, a, b);
        }

        private Expression parseAllAnyPredicate()
        {

            int type = iToken;

            read();
            readThis(Expression.OPEN);

            Expression b = null;
            int brackets = 0;

            if (iToken == Expression.OPEN)
            {
                brackets += parseOpenBrackets() + 1;

                read();
            }

            if (iToken != Expression.SELECT)
            {
                throw Trace.error(Trace.INVALID_IDENTIFIER);
            }

            SubQuery sq = parseSubquery(brackets, null, false, type);
            Select select = sq.select;

            // until we support rows
            Trace.check(sq.select.iResultLen == 1, Trace.SINGLE_COLUMN_EXPECTED);

            b = new Expression(sq);

            read();
            readThis(Expression.CLOSE);

            return new Expression(type, b, null);
        }

        /**
         *  Method declaration
         *
         * @param  type
         * @throws  HsqlException
         */
        private void readThis(int type)
        {
            Trace.check(iToken == type, Trace.UNEXPECTED_TOKEN);
            read();
        }

        /**
         *  Method declaration
         *
         * @return a concatenation, possibly degenerate
         * @throws  HsqlException
         */
        private Expression readConcat()
        {

            Expression r = readSum();

            while (iToken == Expression.CONCAT)
            {
                int type = Expression.CONCAT;
                Expression a = r;

                read();

                r = new Expression(type, a, readSum());
            }

            return r;
        }

        static Dictionary<string, string> simpleFunctions = new Dictionary<string, string>();

        static Parser()
        {
            simpleFunctions.Add(Token.T_CURRENT_DATE,
                                "EffiProz.Core.Library.curdate");
            simpleFunctions.Add(Token.T_CURRENT_TIME,
                                "EffiProz.Core.Library.curtime");
            simpleFunctions.Add(Token.T_CURRENT_TIMESTAMP,
                                "EffiProz.Core.Library.now");
            simpleFunctions.Add(Token.T_CURRENT_USER, "EffiProz.Core.Library.user");
            simpleFunctions.Add(Token.T_SYSDATE, "EffiProz.Core.Library.curdate");
            simpleFunctions.Add(Token.T_NOW, "EffiProz.Core.Library.now");
            simpleFunctions.Add(Token.T_TODAY, "EffiProz.Core.Library.curdate");

            tokenSet.Add(Token.T_COMMA, Expression.COMMA);
            tokenSet.Add(Token.T_EQUALS, Expression.EQUAL);
            tokenSet.Add("!=", Expression.NOT_EQUAL);
            tokenSet.Add("<>", Expression.NOT_EQUAL);
            tokenSet.Add("<", Expression.SMALLER);
            tokenSet.Add(">", Expression.BIGGER);
            tokenSet.Add("<=", Expression.SMALLER_EQUAL);
            tokenSet.Add(">=", Expression.BIGGER_EQUAL);
            tokenSet.Add(Token.T_AND, Expression.AND);
            tokenSet.Add(Token.T_NOT, Expression.NOT);
            tokenSet.Add(Token.T_OR, Expression.OR);
            tokenSet.Add(Token.T_ALL, Expression.ALL);
            tokenSet.Add(Token.T_ANY, Expression.ANY);
            tokenSet.Add(Token.T_IN, Expression.IN);
            tokenSet.Add(Token.T_EXISTS, Expression.EXISTS);
            tokenSet.Add(Token.T_BETWEEN, Expression.BETWEEN);
            tokenSet.Add(Token.T_PLUS, Expression.PLUS);
            tokenSet.Add("-", Expression.NEGATE);
            tokenSet.Add(Token.T_MULTIPLY, Expression.MULTIPLY);
            tokenSet.Add("/", Expression.DIVIDE);
            tokenSet.Add("||", Expression.CONCAT);
            tokenSet.Add(Token.T_OPENBRACKET, Expression.OPEN);
            tokenSet.Add(Token.T_CLOSEBRACKET, Expression.CLOSE);
            tokenSet.Add(Token.T_SELECT, Expression.SELECT);
            tokenSet.Add(Token.T_LIKE, Expression.LIKE);
            tokenSet.Add(Token.T_COUNT, Expression.COUNT);
            tokenSet.Add(Token.T_SUM, Expression.SUM);
            tokenSet.Add(Token.T_MIN, Expression.MIN);
            tokenSet.Add(Token.T_MAX, Expression.MAX);
            tokenSet.Add(Token.T_AVG, Expression.AVG);
            tokenSet.Add(Token.T_EVERY, Expression.EVERY);
            tokenSet.Add(Token.T_SOME, Expression.SOME);
            tokenSet.Add(Token.T_STDDEV_POP, Expression.STDDEV_POP);
            tokenSet.Add(Token.T_STDDEV_SAMP, Expression.STDDEV_SAMP);
            tokenSet.Add(Token.T_VAR_POP, Expression.VAR_POP);
            tokenSet.Add(Token.T_VAR_SAMP, Expression.VAR_SAMP);
            tokenSet.Add(Token.T_IFNULL, Expression.IFNULL);
            tokenSet.Add(Token.T_NVL, Expression.IFNULL);
            tokenSet.Add(Token.T_NULLIF, Expression.NULLIF);
            tokenSet.Add(Token.T_CONVERT, Expression.CONVERT);
            tokenSet.Add(Token.T_CAST, Expression.CAST);
            tokenSet.Add(Token.T_NEXT, Expression.SEQUENCE);
            tokenSet.Add(Token.T_CASE, Expression.CASE);
            tokenSet.Add(Token.T_WHEN, Expression.WHEN);
            tokenSet.Add(Token.T_THEN, Expression.THEN);
            tokenSet.Add(Token.T_ELSE, Expression.ELSE);
            tokenSet.Add(Token.T_END, Expression.ENDWHEN);
            tokenSet.Add(Token.T_CASEWHEN, Expression.CASEWHEN);
            tokenSet.Add(Token.T_COALESCE, Expression.COALESCE);
            tokenSet.Add(Token.T_EXTRACT, Expression.EXTRACT);
            tokenSet.Add(Token.T_POSITION, Expression.POSITION);
            tokenSet.Add(Token.T_FROM, Expression.FROM);
            tokenSet.Add(Token.T_TRIM, Expression.TRIM);
            tokenSet.Add(Token.T_SUBSTRING, Expression.SUBSTRING);
            tokenSet.Add(Token.T_FOR, Expression.FOR);
            tokenSet.Add(Token.T_AS, Expression.AS);
            tokenSet.Add(Token.T_IS, Expression.IS);
            tokenSet.Add(Token.T_QUESTION, Expression.PARAM);
        }

        /**
         *  Method declaration
         *
         * @return  a summation, possibly degenerate
         * @throws  HsqlException
         */
        private Expression readSum()
        {

            Expression r = readFactor();

            while (true)
            {
                int type;

                if (iToken == Expression.PLUS)
                {
                    type = Expression.ADD;
                }
                else if (iToken == Expression.NEGATE)
                {
                    type = Expression.SUBTRACT;
                }
                else
                {
                    break;
                }

                Expression a = r;

                read();

                r = new Expression(type, a, readFactor());
            }

            return r;
        }

        /**
         *  Method declaration
         *
         * @return  a product, possibly degenerate
         * @throws  HsqlException
         */
        private Expression readFactor()
        {

            Expression r = readTerm();

            while (iToken == Expression.MULTIPLY || iToken == Expression.DIVIDE)
            {
                int type = iToken;
                Expression a = r;

                read();

                r = new Expression(type, a, readTerm());
            }

            return r;
        }

        /**
         *  Method declaration
         *
         * @return  a term, possibly composite
         * @throws  HsqlException
         */
        private Expression readTerm()
        {

            Expression r = null;

            switch (iToken)
            {

                case Expression.COLUMN:
                    {
                        r = readColumnExpression();

                        break;
                    }
                case Expression.NEGATE:
                    {
                        int type = iToken;

                        read();

                        r = new Expression(type, readTerm(), null);

                        Trace.check(!r.getArg().isParam(),
                                    Trace.Expression_resolveTypes1);

                        break;
                    }
                case Expression.PLUS:
                    {
                        read();

                        r = readTerm();

                        Trace.check(!r._isParam(), Trace.UNRESOLVED_PARAMETER_TYPE,
                                    Trace.getMessage(Trace.Expression_resolveTypes1));

                        break;
                    }
                case Expression.OPEN:
                    {
                        read();

                        r = readOr();

                        if (iToken != Expression.CLOSE)
                        {
                            throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
                        }

                        read();

                        break;
                    }
                case Expression.VALUE:
                    {
                        r = new Expression(iType, oData);

                        read();

                        break;
                    }
                case Expression.PARAM:
                    {
                        r = new Expression(Types.NULL, null, true, null);

                        parameters.Add(r);
                        read();

                        break;
                    }
                case Expression.NAMED_PARAM:
                    {
                        r = new Expression(Types.NULL, null, true, sToken);

                        parameters.Add(r);
                        read();

                        break;
                    }
                case Expression.SELECT:
                    {
                        SubQuery sq = parseSubquery(0, null, false, Expression.SELECT);

                        r = new Expression(sq);

                        read();

                        break;
                    }
                case Expression.ANY:
                case Expression.ALL:
                    {
                        r = parseAllAnyPredicate();

                        //                read();
                        break;
                    }
                case Expression.MULTIPLY:
                    {
                        r = new Expression(sSchema, sTable, (String)null);

                        read();

                        break;
                    }
                case Expression.CASEWHEN:
                    return readCaseWhenExpression();

                case Expression.CASE:
                    return readCaseExpression();

                case Expression.NULLIF:
                    return readNullIfExpression();

                case Expression.COALESCE:
                case Expression.IFNULL:
                    return readCoalesceExpression();

                case Expression.SEQUENCE:
                    return readSequenceExpression();

                case Expression.CAST:
                case Expression.CONVERT:
                    return readCastExpression();

                case Expression.EXTRACT:
                    return readExtractExpression();

                case Expression.TRIM:
                    return readTrimExpression();

                case Expression.POSITION:
                    return readPositionExpression();

                case Expression.SUBSTRING:
                    return readSubstringExpression();

                default:
                    if (Expression.isAggregate(iToken))
                    {
                        return readAggregate();
                    }
                    else
                    {
                        throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
                    }
            }

            return r;
        }

        /**
         * reads a CASE .. WHEN expression
         */
        Expression readCaseExpression()
        {

            //int type = Expression.CASEWHEN;
            Expression r = null;
            Expression predicand = null;

            read();

            if (iToken != Expression.WHEN)
            {
                predicand = readOr();
            }

            Expression leaf = null;

            while (true)
            {
                Expression casewhen = parseCaseWhen(predicand);

                if (r == null)
                {
                    r = casewhen;
                }
                else
                {
                    leaf.setRightExpression(casewhen);
                }

                leaf = casewhen.getRightExpression();

                if (iToken != Expression.WHEN)
                {
                    break;
                }
            }

            if (iToken == Expression.ELSE)
            {
                readThis(Expression.ELSE);

                Expression elsexpr = readOr();

                leaf.setRightExpression(elsexpr);
            }

            readThis(Expression.ENDWHEN);

            return r;
        }

        /**
         * Reads part of a CASE .. WHEN  expression
         */
        private Expression parseCaseWhen(Expression r)
        {

            readThis(Expression.WHEN);

            Expression condition;

            if (r == null)
            {
                condition = readOr();
            }
            else
            {
                condition = new Expression(Expression.EQUAL, r, readOr());
            }

            readThis(Expression.THEN);

            Expression current = readOr();
            Expression alternatives = new Expression(Expression.ALTERNATIVE,
                current, new Expression(Types.NULL, null));
            Expression casewhen = new Expression(Expression.CASEWHEN, condition,
                                                 alternatives);

            return casewhen;
        }

        /**
         * reads a CASEWHEN expression
         */
        private Expression readCaseWhenExpression()
        {

            int type = iToken;
            Expression r = null;

            read();
            readThis(Expression.OPEN);

            r = readOr();

            readThis(Expression.COMMA);

            Expression thenelse = readOr();

            readThis(Expression.COMMA);

            // thenelse part is never evaluated; only init
            thenelse = new Expression(Expression.ALTERNATIVE, thenelse, readOr());
            r = new Expression(type, r, thenelse);

            readThis(Expression.CLOSE);

            return r;
        }

        /**
         * Reads a CAST or CONVERT expression
         */
        private Expression readCastExpression()
        {

            bool isConvert = iToken == Expression.CONVERT;

            read();
            readThis(Expression.OPEN);

            Expression r = readOr();

            if (isConvert)
            {
                readThis(Expression.COMMA);
            }
            else
            {
                readThis(Expression.AS);
            }

            int typeNr = Types.getTypeNr(sToken);
            int length = 0;
            int scale = 0;
            bool hasLength = false;

            if (Types.acceptsPrecisionCreateParam(typeNr)
                    && tokenizer.isGetThis(Token.T_OPENBRACKET))
            {
                length = tokenizer.getInt();
                hasLength = true;

                if (Types.acceptsScaleCreateParam(typeNr)
                        && tokenizer.isGetThis(Token.T_COMMA))
                {
                    scale = tokenizer.getInt();
                }

                tokenizer.getThis(Token.T_CLOSEBRACKET);
            }

            if (typeNr == Types.FLOAT && length > 53)
            {
                throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
            }

            if (typeNr == Types.TIMESTAMP)
            {
                if (!hasLength)
                {
                    length = 6;
                }
                else if (length != 0 && length != 6)
                {
                    throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                }
            }

            if (r._isParam())
            {
                r.setDataType(typeNr);
            }

            r = new Expression(r, typeNr, length, scale);

            read();
            readThis(Expression.CLOSE);

            return r;
        }

        /**
         * reads a Column or Function expression
         */
        private Expression readColumnExpression()
        {

            string name = sToken;
            Expression r = new Expression(sTable, name, wasQuoted);

            read();

            if (iToken == Expression.OPEN)
            {
                string javaName = database.getJavaName(name);
                Function f = new Function(name, javaName, false);

                session.check(javaName);

                int len = f.getArgCount();
                int i = 0;

                read();

                if (iToken != Expression.CLOSE)
                {
                    while (true)
                    {
                        f.setArgument(i++, readOr());

                        if (iToken != Expression.COMMA)
                        {
                            break;
                        }

                        read();
                    }
                }

                readThis(Expression.CLOSE);

                r = new Expression(f);
            }
            else
            {
                string javaName = null; 

                if (simpleFunctions.TryGetValue(name, out javaName))
                {
                    Function f = new Function(name, javaName, true);

                    r = new Expression(f);
                }
            }

            return r;
        }

        /**
         * reads a CONCAT expression
         */
        private Expression readConcatExpression()
        {

            int type = iToken;

            read();
            readThis(Expression.OPEN);

            Expression r = readOr();

            readThis(Expression.COMMA);

            r = new Expression(type, r, readOr());

            readThis(Expression.CLOSE);

            return r;
        }

        /**
         * Reads a NULLIF expression
         */
        private Expression readNullIfExpression()
        {

            // turn into a CASEWHEN
            read();
            readThis(Expression.OPEN);

            Expression r = readOr();

            readThis(Expression.COMMA);

            Expression thenelse = new Expression(Expression.ALTERNATIVE,
                                                 new Expression(Types.NULL, null),
                                                 r);

            r = new Expression(Expression.EQUAL, r, readOr());
            r = new Expression(Expression.CASEWHEN, r, thenelse);

            readThis(Expression.CLOSE);

            return r;
        }

        /**
         * Reads a COALESE or IFNULL expression
         */
        private Expression readCoalesceExpression()
        {

            Expression r = null;

            // turn into a CASEWHEN
            read();
            readThis(Expression.OPEN);

            Expression leaf = null;

            while (true)
            {
                Expression current = readOr();

                if (leaf != null && iToken == Expression.CLOSE)
                {
                    readThis(Expression.CLOSE);
                    leaf.setLeftExpression(current);

                    break;
                }

                Expression condition = new Expression(Expression.IS_NULL, current,
                                                      null);
                Expression alternatives = new Expression(Expression.ALTERNATIVE,
                    new Expression(Types.NULL, null), current);
                Expression casewhen = new Expression(Expression.CASEWHEN,
                                                     condition, alternatives);

                if (r == null)
                {
                    r = casewhen;
                }
                else
                {
                    leaf.setLeftExpression(casewhen);
                }

                leaf = alternatives;

                readThis(Expression.COMMA);
            }

            return r;
        }

        /**
         * Reads an EXTRACT expression
         */
        private Expression readExtractExpression()
        {

            read();
            readThis(Expression.OPEN);

            string name = sToken;

            // must be an accepted identifier
            if (!Expression.SQL_EXTRACT_FIELD_NAMES.Contains(name))
            {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
            }

            readToken();
            readThis(Expression.FROM);

            // the name argument is DAY, MONTH etc.  - OK for now for CHECK constraints
            Function f = new Function(name, database.getJavaName(name), false);

            f.setArgument(0, readOr());
            readThis(Expression.CLOSE);

            return new Expression(f);
        }

        /**
         * Reads a POSITION expression
         */
        private Expression readPositionExpression()
        {

            read();
            readThis(Expression.OPEN);

            Function f = new Function(Token.T_POSITION,
                                      "EffiProz.Core.Library.position", false);

            f.setArgument(0, readTerm());
            readThis(Expression.IN);
            f.setArgument(1, readOr());
            readThis(Expression.CLOSE);

            return new Expression(f);
        }

        /**
         * Reads a SUBSTRING expression
         */
        private Expression readSubstringExpression()
        {

            bool commas = false;

            read();
            readThis(Expression.OPEN);

            // OK for now for CHECK search conditions
            Function f = new Function(Token.T_SUBSTRING,
                                      "EffiProz.Core.Library.Substring", false);

            f.setArgument(0, readTerm());

            if (iToken == Expression.FROM)
            {
                readThis(Expression.FROM);
            }
            else
            {
                readThis(Expression.COMMA);

                commas = true;
            }

            f.setArgument(1, readOr());

            Expression count = null;

            if (!commas && iToken == Expression.FOR)
            {
                readThis(Expression.FOR);

                count = readTerm();
            }
            else if (commas && iToken == Expression.COMMA)
            {
                readThis(Expression.COMMA);

                count = readTerm();
            }

            f.setArgument(2, count);
            readThis(Expression.CLOSE);

            return new Expression(f);
        }

        private Expression readSequenceExpression()
        {

            tokenizer.getThis(Token.T_VALUE);
            tokenizer.getThis(Token.T_FOR);

            string name = tokenizer.getName();
            string schemaname = tokenizer.getLongNameFirst();

            schemaname = session.getSchemaName(schemaname);

            // Read next because Tokenizer.back() will run after this.
            // (This is because usually when reading expressions, you need to
            // get the following token to know whether you have finished.
            tokenizer.getString();

            NumberSequence sequence = database.schemaManager.getSequence(name,
                schemaname);

            return new Expression(sequence);
        }

        /**
         * Reads a TRIM expression
         */
        private Expression readTrimExpression()
        {

            read();
            readThis(Expression.OPEN);

            string type = sToken;

            if (Expression.SQL_TRIM_SPECIFICATION.Contains(type))
            {
                read();
            }
            else
            {
                type = Token.T_BOTH;
            }

            string trimstr;

            if (sToken.Length == 1)
            {
                trimstr = sToken;

                read();
            }
            else
            {
                trimstr = " ";
            }

            readThis(Expression.FROM);

            Expression trim = new Expression(Types.CHAR, trimstr);
            Expression leading;
            Expression trailing;

            if (type.Equals(Token.T_LEADING))
            {
                leading = new Expression(true);
                trailing = new Expression(false);
            }
            else if (type.Equals(Token.T_TRAILING))
            {
                leading = new Expression(false);
                trailing = new Expression(true);
            }
            else
            {
                leading = trailing = new Expression(true);
            }

            // name argument is OK for now for CHECK constraints
            Function f = new Function(Token.T_TRIM, "EffiProz.Core.Library.trim",
                                      false);

            f.setArgument(0, readOr());
            f.setArgument(1, trim);
            f.setArgument(2, leading);
            f.setArgument(3, trailing);
            readThis(Expression.CLOSE);

            return new Expression(f);
        }

        /**
         *  Reads a DEFAULT clause expression.
         */
        public Expression readDefaultClause(int dataType)
        {
  
            read();

            switch (iToken)
            {

                case Expression.COLUMN:
                    {
                        string name = sToken;
                        string javaName = (String)simpleFunctions[name];

                        if (javaName != null)
                        {
                            Function f = new Function(name, javaName, true);

                            return new Expression(f);
                        }

                        break;
                    }
                case Expression.NEGATE:
                    {
                        int exprType = iToken;

                        read();

                        if (iToken == Expression.VALUE)
                        {
                            oData = Column.convertObject(oData, dataType);

                            return new Expression(exprType,
                                                  new Expression(dataType, oData),
                                                  null);
                        }

                        break;
                    }
                case Expression.VALUE:
                    {
                        string name = sToken.ToUpper();
                        string javaName = null;

                        if (!simpleFunctions.TryGetValue(name, out javaName))
                        {
                            javaName = null;
                        }

                        if (Types.isDatetimeType(dataType) && javaName != null)
                        {
                            Function f = new Function(name, javaName, true);

                            return new Expression(f);
                        }

                        oData = Column.convertObject(oData, dataType);

                        return new Expression(dataType, oData);
                    }
            }

            throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE, sToken);
        }

        /**
         *  Method declaration
         *
         * @throws  HsqlException
         */
        private void read()
        {

            sToken = tokenizer.getString();
            wasQuoted = tokenizer.wasQuotedIdentifier();

            if (tokenizer.wasValue())
            {
                iToken = Expression.VALUE;
                oData = tokenizer.getAsValue();
                iType = tokenizer.getType();
            }
            else if (tokenizer.wasSimpleName())
            {
                iToken = Expression.COLUMN;
                sTable = null;
            }
            else if (tokenizer.wasLongName())
            {
                sSchema = tokenizer.getLongNamePre();
                sTable = tokenizer.getLongNameFirst();

                if (sToken.Equals(Token.T_MULTIPLY))
                {
                    iToken = Expression.MULTIPLY;
                }
                else
                {
                    iToken = Expression.COLUMN;
                }
            }
            else if (tokenizer.wasParameter())
            {
                iToken = Expression.NAMED_PARAM;
            }
            else if (sToken.Length == 0)
            {
                iToken = Expression.END;
            }
            else
            {
                iToken =  -1;

                if (!tokenSet.TryGetValue(sToken, out iToken))
                {
                    iToken = Expression.END;
                }

                switch (iToken)
                {

                    case Expression.COMMA:
                    case Expression.EQUAL:
                    case Expression.NOT_EQUAL:
                    case Expression.SMALLER:
                    case Expression.BIGGER:
                    case Expression.SMALLER_EQUAL:
                    case Expression.BIGGER_EQUAL:
                    case Expression.AND:
                    case Expression.OR:
                    case Expression.NOT:
                    case Expression.ALL:
                    case Expression.ANY:
                    case Expression.IN:
                    case Expression.EXISTS:
                    case Expression.BETWEEN:
                    case Expression.PLUS:
                    case Expression.NEGATE:
                    case Expression.DIVIDE:
                    case Expression.CONCAT:
                    case Expression.OPEN:
                    case Expression.CLOSE:
                    case Expression.SELECT:
                    case Expression.LIKE:
                    case Expression.COUNT:
                    case Expression.SUM:
                    case Expression.MIN:
                    case Expression.MAX:
                    case Expression.AVG:
                    case Expression.EVERY:
                    case Expression.SOME:
                    case Expression.STDDEV_POP:
                    case Expression.STDDEV_SAMP:
                    case Expression.VAR_POP:
                    case Expression.VAR_SAMP:
                    case Expression.CONVERT:
                    case Expression.CAST:
                    case Expression.SEQUENCE:
                    case Expression.IFNULL:
                    case Expression.COALESCE:
                    case Expression.NULLIF:
                    case Expression.CASE:
                    case Expression.WHEN:
                    case Expression.THEN:
                    case Expression.ELSE:
                    case Expression.ENDWHEN:
                    case Expression.CASEWHEN:
                    case Expression.EXTRACT:
                    case Expression.POSITION:
                    case Expression.SUBSTRING:
                    case Expression.FROM:
                    case Expression.FOR:
                    case Expression.END:
                    case Expression.PARAM:
                    case Expression.TRIM:
                    case Expression.LEADING:
                    case Expression.TRAILING:
                    case Expression.BOTH:
                    case Expression.AS:
                    case Expression.IS:
                    case Expression.DISTINCT:
                        break;            // nothing else required, iToken initialized properly

                    case Expression.MULTIPLY:
                        sTable = null;    // _in case of ASTERIX
                        break;

                    default:
                        iToken = Expression.END;
                        break;
                }
            }
        }

        /**
         * A workaround for parsing EXTRACT clause elements such as MONTH, DAY
         * and YEAR, without having to make each of them SQL KEYWORDS _in Tokenizer.
         *
         * @  if a tokenization error occurs
         */
        private void readToken()
        {
            sToken = tokenizer.getString();
            iToken = -1;
            if (!tokenSet.TryGetValue(sToken, out iToken))
                iToken = -1;
        }

        private static Dictionary<string, int> tokenSet = new Dictionary<string, int>(37);

      
        // boucherb@users 20030411 - patch 1.7.2 - for prepared statements
        // ---------------------------------------------------------------
        List<Expression> parameters = new List<Expression>();
        private static  Expression[] noParameters = new Expression[0];
        private static  SubQuery[] noSubqueries = new SubQuery[0];

        /**
         *  Destructive get method
         */
        public Expression[] getParameters()
        {

            Expression[] result = parameters.Count == 0 ? noParameters
                                                         : (Expression[])parameters.ToArray();

            parameters.Clear();

            return result;
        }

        void clearParameters()
        {
            parameters.Clear();
        }

        // fredt - new implementation of subquery list

        /**
         * Sets the subqueries as belonging to the View being constructed
         */
        public void setAsView(View view)
        {

            foreach (var sq in subQueryList)
            {

                if (sq.view == null)
                {
                    sq.view = view;
                }
            }
        }

        /**
         * Return the list of subqueries as an array sorted according to the order
         * of materialization, then clear the internal subquery list
         */
        public SubQuery[] getSortedSubqueries()
        {

            if (subQueryList.Count == 0)
            {
                return noSubqueries;
            }

            subQueryList.Sort(SubQuery.compare);

            SubQuery[] subqueries;

            subqueries = subQueryList.ToArray();
            subQueryList.Clear();

            return subqueries;
        }

        /**
         * Retrieves a CALL-type CompiledStatement from this parse context.
         */
        public CompiledStatement compileCallStatement()
        {

            clearParameters();

            Expression expression = parseExpression();
            CompiledStatement cs = new CompiledStatement(session, database,
                session.currentSchema, expression, getSortedSubqueries(),
                getParameters());

            return cs;
        }

        /**
         * Retrieves a DELETE-type CompiledStatement from this parse context.
         */
        public CompiledStatement compileDeleteStatement()
        {

            string token;
            Expression condition = null;
            TableFilter tableFilter;

            clearParameters();
            tokenizer.getThis(Token.T_FROM);

            tableFilter = parseSimpleTableFilter(GrantConstants.DELETE);
            token = tokenizer.getString();

            if (token.Equals(Token.T_WHERE))
            {
                condition = parseExpression();
            }
            else
            {
                tokenizer.back();
            }

            CompiledStatement cs = new CompiledStatement(session, database,
                session.currentSchema, tableFilter, condition,
                getSortedSubqueries(), getParameters());

            return cs;
        }

        private void getInsertColumnValueExpressions(Table t, Expression[] acve,
                int len)
        {

            tokenizer.getThis(Token.T_OPENBRACKET);

            for (int i = 0; i < len; i++)
            {
                Expression columnValExpression = parseExpression();

                columnValExpression.resolveTables(null);
                columnValExpression.resolveTypes(session);

                acve[i] = columnValExpression;

                string token = tokenizer.getSimpleToken();

                if (token.Equals(Token.T_COMMA))
                {
                    continue;
                }

                if (token.Equals(Token.T_CLOSEBRACKET))
                {
                    if (i == len - 1)
                    {
                        return;
                    }
                    else
                    {
                        break;
                    }
                }

                tokenizer.throwUnexpected();
            }

            throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
        }

        /**
         * Retrieves an INSERT_XXX-type CompiledStatement from this parse context.
         */
        public CompiledStatement compileInsertStatement()
        {

            clearParameters();
            tokenizer.getThis(Token.T_INTO);

            List<object> columnNames;
            bool[] columnCheckList;
            int[] columnMap;
            int len;
            string token = tokenizer.getName();
            string schema = session.getSchemaName(tokenizer.getLongNameFirst());
            Table table = database.schemaManager.getTable(session, token, schema);

            checkTableWriteAccess(table, GrantConstants.INSERT);

            columnNames = null;
            columnCheckList = null;
            columnMap = table.getColumnMap();
            len = table.getColumnCount();

            int brackets = parseOpenBrackets();

            token = tokenizer.getString();

            if (brackets == 1 && !tokenizer.wasThis(Token.T_SELECT))
            {
                brackets = 0;

                tokenizer.back();

                columnNames = getColumnNames(database, table, tokenizer, false);

                if (columnNames.Count > len)
                {
                    throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
                }

                len = columnNames.Count;
                columnCheckList = table.getNewColumnCheckList();
                columnMap = new int[len];

                for (int i = 0; i < len; i++)
                {
                    int ci = table.getColumnNr((String)columnNames[(i)]);

                    columnMap[i] = ci;
                    columnCheckList[ci] = true;
                }

                token = tokenizer.getSimpleToken();
            }
            else if (!tokenizer.wasSimpleToken())
            {
                tokenizer.throwUnexpected();
            }

            int command = Token.get(token);

            switch (command)
            {

                case Token.VALUES:
                    {
                        Expression[] acve = new Expression[len];

                        getInsertColumnValueExpressions(table, acve, len);

                        CompiledStatement cs =
                            new CompiledStatement(session.currentSchema, table,
                                                  columnMap, acve, columnCheckList,
                                                  getSortedSubqueries(),
                                                  getParameters());

                        return cs;
                    }
                case Token.OPENBRACKET:
                    {
                        brackets = parseOpenBrackets() + 1;

                        tokenizer.getThis(Token.T_SELECT);
                    }
                    goto case Token.SELECT;
                case Token.SELECT:
                    {

                        // accept ORDER BY or ORDRY BY with LIMIT
                        Select select = parseSelect(brackets, true, false, true, true);

                        if (len != select.iResultLen)
                        {
                            throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
                        }

                        CompiledStatement cs = new CompiledStatement(session,
                            database, session.currentSchema, table, columnMap,
                            columnCheckList, select, getSortedSubqueries(),
                            getParameters());

                        return cs;
                    }
                default:
                    {
                        throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                    }
            }
            
        }

        /**
         * Retrieves a SELECT-type CompiledStatement from this parse context.
         */
        public CompiledStatement compileSelectStatement(int brackets)
        {

            clearParameters();

            Select select = parseSelect(brackets, true, true, false, true);

            if (select.sIntoTable != null)
            {
                string name = select.sIntoTable.name;
                string schema = select.sIntoTable.schema.name;

                if (database.schemaManager.findUserTable(session, name, schema)
                        != null)
                {
                    throw Trace.error(Trace.TABLE_ALREADY_EXISTS, name);
                }
            }

            CompiledStatement cs = new CompiledStatement(session, database,
                session.currentSchema, select, getSortedSubqueries(),
                getParameters());

            return cs;
        }

        /**
         * Retrieves an UPDATE-type CompiledStatement from this parse context.
         */
        public CompiledStatement compileUpdateStatement()
        {

            string token;
            Table table;
            int[] colList;
            Expression[] exprList;
            int len;
            Expression cve;
            Expression condition;

            clearParameters();

            TableFilter tableFilter = parseSimpleTableFilter(GrantConstants.UPDATE);

            table = tableFilter.filterTable;

            tokenizer.getThis(Token.T_SET);

            colList = table.getNewColumnMap();
            exprList = new Expression[colList.Length];
            len = 0;
            token = null;

            do
            {
                int ci = table.getColumnNr(tokenizer.getName());
                string tablename = tokenizer.getLongNameFirst();

                if (tablename != null
                        && !tableFilter.getName().Equals(tablename))
                {
                    throw Trace.error(Trace.TABLE_NOT_FOUND);
                }

                tokenizer.getThis(Token.T_EQUALS);

                cve = parseExpression();

                if (len == colList.Length)
                {

                    // too many (repeat) assignments
                    throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH);
                }

                colList[len] = ci;
                exprList[len] = cve;
                token = tokenizer.getSimpleToken();

                len++;
            } while (token.Equals(Token.T_COMMA));

            condition = null;

            if (token.Equals(Token.T_WHERE))
            {
                condition = parseExpression();
            }
            else
            {
                tokenizer.back();
            }

            colList = (int[])ArrayUtil.resizeArray(colList, len);
            exprList = (Expression[])ArrayUtil.resizeArray(exprList, len);

            CompiledStatement cs = new CompiledStatement(session, database,
                session.currentSchema, tableFilter, colList, exprList, condition,
                getSortedSubqueries(), getParameters());

            return cs;
        }

        public int parseOpenBracketsSelect()
        {

            int count = parseOpenBrackets();

            tokenizer.getThis(Token.T_SELECT);

            return count;
        }

        public int parseOpenBrackets()
        {

            int count = 0;

            while (tokenizer.isGetThis(Token.T_OPENBRACKET))
            {
                count++;
            }

            return count;
        }

        public int parseCloseBrackets(int limit)
        {

            int count = 0;

            while (count < limit && tokenizer.isGetThis(Token.T_CLOSEBRACKET))
            {
                count++;
            }

            return count;
        }

        public HashMappedList<string, Expression> parseColumnList()
        {
            return processColumnList(tokenizer, false);
        }

        public static HashMappedList<string, Expression> processColumnList(Tokenizer tokenizer,
                bool acceptAscDesc)
        {

            HashMappedList<string, Expression> list;
            string token;

            list = new HashMappedList<string, Expression>();

            tokenizer.getThis(Token.T_OPENBRACKET);

            while (true)
            {
                token = tokenizer.getSimpleName();



                if (list.ContainsKey(token))
                {
                    throw Trace.error(Trace.COLUMN_ALREADY_EXISTS, token);
                }

                list.Add(token, null);

                token = tokenizer.getSimpleToken();

                if (acceptAscDesc
                        && (token.Equals(Token.T_DESC)
                            || token.Equals(Token.T_ASC)))
                {
                    token = tokenizer.getSimpleToken();    // OJ: eat it up
                }

                if (token.Equals(Token.T_COMMA))
                {
                    continue;
                }

                if (token.Equals(Token.T_CLOSEBRACKET))
                {
                    break;
                }

                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }

            return list;
        }
    }
}
